import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPool {
    public static void main(String[] args) {
        // 使用一下标准库的线程池

        // 创建一个线程池 (创建的是 池子里面有固定的数量 )
       ExecutorService pool = Executors.newFixedThreadPool(10); //Executors 这个提供的是静态线程 , 写一个10呢 就是表示 里面有 10 个线程
        // newFixedThreadPool 这个 操作 是 使用某个类的静态方法 直接构造出一个对象来,相当于是把new操作 给隐藏到这样的方法后面
        // 像这样的方法 称之为"工厂方法" , 提供这个方法的类称为"工厂类"
        // 此处这个代码就使用了 "工厂模式"这种设计模式

        // 工厂模式 : 一句话表示 , 使用普通方法 来代替构造方法,创建对象(用普通方法就可以提供多个不同的方法名了,此时也没有参数要求区分了)
        // (因为构造方法是有坑的, 坑就体现在,如果只构造一个对象就好办,如果要构造多个不同情况的对象 就不好办了)
        // (多个构造方法 是要通过 重载 的方式来提供的,重载要求方法名相同 参数个数或者类型不同)
        // (为了解决这个问题,就可以使用工厂模式) (在java 方法的重写是和父类子类相关的(如果是其他语言就不一定),用一个新的方法就是一个新的方法来代替旧的方法
        //  就得要求新方法和就方法.名字/参数都得一摸一样)

        //线程池提供了一个重要的方法,submit,可以给线程提交若干个任务

        for (int i = 0; i < 1000; i++) { // 也可以通过for循环来循环进行
            int n = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello "+ n); // <--- 这里面不能直接使用 i 因为是由于 lambada表达式的 变量捕获
                    // i 是 存在于主线程的栈中的 , 在运行完之后 会被销毁 , 这里的Runnable方法并不是立刻执行的
                    // 而是在未来的某一个节点执行 , 为了避免作用域的差异 导致后续的 run 方法执行 i 的时候 i 就销毁了 于是就有了变量捕获
                    // 所以要把 i 再拷贝一份
                }
            }); // 运行之后 并没有发现进程结束
            // 原因是 , main线程结束了,但是整个进程没有结束,线程池中的线程 都是前台线程.此时会阻止其进程的结束 , 定时器也是这样的

            //加上for循环之后 会发现这1000个任务 是由 十个线程一块执行的
            // 将这个 1000 个任务放在线程池里面 , 差不多是每个线程执行100个,但是也不是严格平均的,多一个少一个都很正常
            // 由于每个任务的执行时间都差不多,因此每个线程做的任务的数量就差不多
        }

        // Executors 可以创建好几种线程池
         // 2. Executors.newCachedThreadPool();// 线程数量是动态变化的 . 如果任务多了 就多搞几个线程 ,如果任务少了,就少搞几个线程
         // 3.Executors.newSingleThreadExecutor(); // 表示线程池里只有一个线程
         // 4.Executors.newScheduledThreadPool(); // 类似于定时器,也是让任务延时执行.只不过执行的时候不是由扫描线程自己执行了,而是由单独的线程池来执行
        // 上述的线程池,本质上都是通过包装ThreadPoolExecutor来实现的, 但是这个线程池用起来更麻烦,所以才提供了工厂类

        // *****  下面是重点面试题  ( 首先在javaSE官方文档中找到java.util.concurrent包) (这个包是和多线程变成密切相关的 juc) ****
        // 在里面找到 ThreadPoolExecutor 这个类
        // 在这个类里面会提供很多的方法, 如 submit 方法
        // 但是还有跟重要的方法  **** 构造方法 ****
        // 其中最复杂的是最后一个版本的构造方法

        // ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
        //创建一个新 ThreadPoolExecutor给定的初始参数。

        // 先说其中的 corePoolSize  和 maximumPoolSize 参数
        // corePoolSize  表示 核心线程数
        // maximumPoolSize 表示最大线程数

        // 再解释 : ThreadPoolExecutor 相当于把里面的线程分成两类:
        // 一类是正式员工(核心线程)    (打比方的)
        // 一类是临时工/实习生
        // 上面这两个之和就是最大线程数
        // 具体解释就是 : 允许正式员工摸鱼,而不允许实习生摸鱼,如果摸鱼了就会被 开除(销毁)
        // 如果任务多 显然更需要更多的人手(更多的线程)
        // 但是一个程序任务不一定始终都很多.有时候多 有时候少
        // 如果任务少了 线程多了,就非常不合适了.... 就需要对现有的线程进行一定的淘汰了


        // 在实际开发中呢,线程池的线程数,设定成多少合适呢?
        // 如果大家看网上的资料 会有一些答案 ,但是这些都是不准确的
        // 不同程序的特点不同,此时要设置的线程数也是不同的
        // 考虑两个极端的情况:
        // 1. CPU 密集型. 每个线程要进行的任务都是狂转CPU(需要进行一系列的算数计算)
        // 此时线程池的线程数,最多也不超过CPU的核数
        // 此时如果你设置的更大也没用
        // 2.IO 密集型,每个线程干的工作就是等待IO (读写硬盘...),不吃CPU
        // 此时这样的线程处于阻塞状态.不参与CPU调度
        // 这时候多搞一些线程都无所谓,不受制于CPU核数的

        // 然而在实际开发中,并没有程序符合这两种理想模型
        // 真实的程序,往往一部分要吃CPU,一部分就要等待IO
        // 具体这个程序,几成工作量 吃CPU 的 几成工作量是等待IO, 所以是不确定的

        // 实践中确定线程数量,也就简单,通过测试/实验的方式~~ 实践是检验真理的唯一标准

       // 下一组参数 long keepAliveTime 和  TimeUnit unit
        //  keepAliveTime表示 描述了临时工可以摸鱼的最大时间数(就是线程什么活也不做)
        // TimeUnit unit 时间单位(ms 分钟)
        // 这两个参数组合就组成了最大摸鱼数

        // 下一个参数 BlockingQueue<Runnable> workQueue 表示 线程池任务队列
        // 此处使用 阻塞队列 也是很容易理解的  想象一下 每个工作线程 都是在不停尝试take
        // 如果有线程就可以take成功,没有 就阻塞 (是可以手动指定的)

        // 下一个参数  ThreadFactory threadFactory  (线程工厂)
        // 使用于创建线程的 线程池也是需要创建线程

        // 下一个参数 RejectedExecutionHandler handler
        // 描述了线程池的"拒绝策略"
        // 也是一个特殊对象,描述了线程池任务队列满了,如果继续添加任务会有啥样的行为...
        // 如何进行拒绝?
        // 在标准库里面 提供了 四种拒绝方式
        // ThreadPoolExecutor.AbortPolicy
        // 如果任务太多 队列满了 就直接抛出异常 (新活旧活啥也不干)

        // ThreadPoolExecutor.CallerRunsPolicy
        // 如果队列满了,多出来的任务,谁加的谁负责执行 (哪个线程加的让哪个线程来)

        // ThreadPoolExecutor.DiscardOldestPolicy
        // 如果队列满了 就会丢弃最早的任务

        // ThreadPoolExecutor.DiscardPolicy
        // 如果队列满了 就会丢弃最新的任务


    }
}
